package com.intervalstudios.wiimotegrabber;

import wiiremotej.WiiRemote;
import wiiremotej.WiiRemoteExtension;
import wiiremotej.event.WRAccelerationEvent;
import wiiremotej.event.WRButtonEvent;
import wiiremotej.event.WRCombinedEvent;
import wiiremotej.event.WRExtensionEvent;
import wiiremotej.event.WRIREvent;
import wiiremotej.event.WRStatusEvent;
import wiiremotej.event.WiiRemoteAdapter;

import java.io.IOException;
import java.util.*;
import java.util.logging.Logger;

import de.sciss.net.OSCMessage;

public class WiiRemoteListenerImpl extends WiiRemoteAdapter {
    private static Logger log = Logger.getLogger("WiiMoteGrabber");

    private Queue<Map<String,Object[]>> messages;
    private String id;

    private double roll;
    private double pitch;
    
    private WiiRemote remote;
    
    private Thread blinker;
    
    private boolean connected = true;
    
    WiiRemoteListenerImpl(Queue<Map<String, Object[]>> messages, String id, WiiRemote remote) {
        this.id = id;
        this.remote = remote;
        setMessages(messages);
        //TODO uncomment this when the disconnect bug is fully resolved, I get an IOException currently: 
        flashLights();
    }
        
    private void flashLights() {
		blinker = new Thread(
			new Runnable() {
				private int index = 1;
				private int failures = 0;

				public void run() {
					boolean[] lightStatus = {true, false, false, false};
					while(remote.isConnected()){
						try{
							int rotation = index % 4;
							remote.setLEDLights(new boolean[]{
										lightStatus[index % 4],
										lightStatus[(index + 1) % 4],
										lightStatus[(index + 2) % 4],
										lightStatus[(index + 3) % 4]
								});
							index ++;
							Thread.sleep(700);
						}catch(IOException e){
							failures++;
							try{
								Thread.sleep(2000);
							}catch(InterruptedException intE){}
				        	log.severe("IOException in flashlights(). Will try again." + e.toString());
				        	if(failures > 5){
					        	log.severe("IOException in flashlights(). Shutting down loop." + e.toString());
					        	break;
				        	}
						}catch(InterruptedException e){
							
						}
					}
				}
			}
		);
		blinker.start();
	}

    @Override
    public void disconnected() {
    	connected = false;
        log.info("WiiMote Disconnected " + getId());
        Map<String,Object[]> message = new HashMap<String,Object[]>();
        message.put("id",new Object[]{getId()});            
        message.put("disconnect",new Object[]{this});
        addMessageToQueue(message);
        throw new Error("Magically, throwing an error here fixes the bug where disconnecting one wiimote kills input from all other wiimotes on XP. Weird.");
    }

    @Override
	public void combinedInputReceived(WRCombinedEvent evt) {
    	if (!evt.getSource().isConnected()){//TODO This might help prevent IOExceptions on disconnect, seems hackish though
    		//return;
    	}
    	log.fine("combinedInputReceived");
    	
        Map<String,Object[]> message = new HashMap<String,Object[]>();

        //Package the id just like other data, remember to special case it later
        message.put("id",new Object[]{getId()});

        //Package the acceleration data if it exists
        WRAccelerationEvent acc = evt.getAccelerationEvent();
        if(acc != null){
        	roll = acc.getRoll();
        	pitch = acc.getPitch();
            message.put("acceleration",
                    new Object[]{
                            acc.getXAcceleration(),
                            acc.getYAcceleration(),
                            acc.getZAcceleration(),
                            roll,
                            pitch});
        }

        //Package the irdata if it exists

        WRIREvent ir = evt.getIREvent();
        if(ir!=null){
            double [] mousePoint = MouseConverter.convertIRtoXY(ir,roll);
            message.put("irdata",
                    new Object[]{
                            mousePoint[0],
                            mousePoint[1]});
        }
        /*  Old style irdata message
            List<Object> irdata = new ArrayList<Object>();
            for(IRLight light : ir.getIRLights()){
                if(light!=null){
                    irdata.add(light.getX());
                    irdata.add(light.getY());
                }
            }
            message.put("irdata", irdata.toArray());
        }*/

        WRButtonEvent buts = evt.getButtonEvent();

        if(buts!=null){
            int[] btnIds = new int[]{
                    WRButtonEvent.A,
                    WRButtonEvent.B,
                    WRButtonEvent.ONE,
                    WRButtonEvent.TWO,
                    WRButtonEvent.PLUS,
                    WRButtonEvent.MINUS,
                    WRButtonEvent.UP,
                    WRButtonEvent.DOWN,
                    WRButtonEvent.LEFT,
                    WRButtonEvent.RIGHT,
                    WRButtonEvent.HOME};
            List<Integer> btnStates = new LinkedList<Integer>();
            for (int btnId : btnIds){
                btnStates.add(buts.isPressed(btnId) ? 1 : 0);
            }
            message.put("buts",btnStates.toArray());
		}

        addMessageToQueue(message);
        
	}

	private void addMessageToQueue(Map<String, Object[]> message) {

		try {
			StringBuffer messageLogBuffer = new StringBuffer()
					.append("Adding message to queue: \n");

			// loop through message and build a string out of it
			for (Map.Entry<String, Object[]> submessage : message.entrySet()) {
				messageLogBuffer.append(submessage.getKey()).append("=");
				for (Object submessageValueItem : submessage.getValue()) {
					messageLogBuffer.append(submessageValueItem.toString());
					messageLogBuffer.append(", ");
				}
				messageLogBuffer.append("\n");
			}

			log.fine(messageLogBuffer.toString());

		} catch (Exception e) {
			log.severe(e.toString());
		}
		getMessages().add(message);
	}

    @Override
	public void accelerationInputReceived(WRAccelerationEvent evt) {
		// TODO Auto-generated method stub
	}

    @Override
	public void IRInputReceived(WRIREvent evt) {
		// TODO Auto-generated method stub

	}

	@Override
	public void buttonInputReceived(WRButtonEvent evt) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void extensionConnected(WiiRemoteExtension extension) {
		// TODO Auto-generated method stub

	}

	@Override
	public void extensionDisconnected(WiiRemoteExtension extension) {
		// TODO Auto-generated method stub

	}

	@Override
	public void extensionInputReceived(WRExtensionEvent evt) {
		// TODO Auto-generated method stub

	}

	@Override
	public void extensionPartiallyInserted() {
		// TODO Auto-generated method stub

	}

	@Override
	public void extensionUnknown() {
		// TODO Auto-generated method stub

	}

	@Override
	public void statusReported(WRStatusEvent evt) {
		// TODO Auto-generated method stub

	}

    public synchronized String getId() {       
        return id;
    }

    public synchronized Queue<Map<String, Object[]>> getMessages(){
        return messages;
    }

    public synchronized void setMessages(Queue<Map<String, Object[]>> messages){
        this.messages = messages;
    }
}
